package io.gitee.kotle.player.core

import android.content.Context
import android.media.AudioFormat
import android.media.AudioManager
import androidx.annotation.OptIn
import androidx.annotation.RawRes
import androidx.lifecycle.Lifecycle
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
import androidx.media3.common.Format
import androidx.media3.common.audio.SonicAudioProcessor
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.cronet.CronetDataSource
import androidx.media3.datasource.cronet.CronetUtil
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.audio.AudioSink
import androidx.media3.exoplayer.audio.DefaultAudioSink
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import io.gitee.kotle.player.core.helper.PlayerBindLifecycleHelper
import io.gitee.kotle.player.download.IPlayerDownload
import java.lang.reflect.Field
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.concurrent.Executors


/**
 *
 * 描述：播放器相关的扩展函数或工具类
 *
 * @author Jiale.Wei
 *
 * 创建时间：2023/4/12 18:15
 *
 */

/**
 * 创建exoPlayer对象
 */
@OptIn(UnstableApi::class)
inline fun createExoPlayer(context: Context, block: (ExoPlayer.Builder) -> Unit = {}): ExoPlayer {
    val applicationContext = context.applicationContext
    return ExoPlayer.Builder(applicationContext, CustomRenderersFactory(applicationContext))
        .setAudioAttributes(
            AudioAttributes.Builder()
                .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
                .setUsage(C.USAGE_MEDIA)
                .build(),
            true
        )
        .setTrackSelector(DefaultTrackSelector(context).also {
            it.setParameters(
                DefaultTrackSelector.Parameters.Builder(context)
                    .build()
            )
        })
        .also {
            //谷歌的http网络框架（需要谷歌服务），类似于okHttp，返回null，则不支持此框架，此时使用默认值
            val croNetEngine = CronetUtil.buildCronetEngine(applicationContext)
            if (croNetEngine != null) {
                it.setMediaSourceFactory(
                    DefaultMediaSourceFactory(applicationContext)
                        .setDataSourceFactory(
                            DefaultDataSource.Factory(
                                applicationContext,
                                CronetDataSource.Factory(
                                    croNetEngine, Executors.newSingleThreadExecutor()
                                )
                            )
                        )
                )
            }
            block.invoke(it)
        }
        .build()
}

@OptIn(UnstableApi::class)
open class CustomRenderersFactory(context: Context) : DefaultRenderersFactory(context) {
    init {
        setExtensionRendererMode(EXTENSION_RENDERER_MODE_ON)
    }

    override fun buildAudioSink(
        context: Context,
        enableFloatOutput: Boolean,
        enableAudioTrackPlaybackParams: Boolean
    ): AudioSink {
        val sonicAudioProcessor = SonicAudioProcessor()
        val defaultAudioSink = DefaultAudioSink.Builder(context)
            .setEnableFloatOutput(enableFloatOutput)
            .setEnableAudioTrackPlaybackParams(enableAudioTrackPlaybackParams)
            .setAudioProcessors(arrayOf(sonicAudioProcessor))
            .build()
        val invocationHandler = RenderersFactoryInvocationHandler(
            context.getSupportMaxOutputSampleRate(),
            defaultAudioSink,
            sonicAudioProcessor
        )
        val cls = defaultAudioSink.javaClass
        val proxyInstance = Proxy.newProxyInstance(
            cls.getClassLoader(),
            cls.interfaces,
            invocationHandler
        ) as AudioSink
        return proxyInstance
    }
}

@OptIn(UnstableApi::class)
class RenderersFactoryInvocationHandler(
    private val maxOutputSampleRateHz: Int,
    private val audioSink: AudioSink,
    private val sonicAudioProcessor: SonicAudioProcessor
) : InvocationHandler {

    override fun invoke(proxy: Any, method: Method, args: Array<Any?>?): Any? {
        if (method.name == "configure") {
            val format = args?.get(0) as? Format
            val currentSampleRate = format?.sampleRate ?: 0
            if (currentSampleRate > maxOutputSampleRateHz) {
                sonicAudioProcessor.setOutputSampleRateHz(maxOutputSampleRateHz)
            } else {
                sonicAudioProcessor.setOutputSampleRateHz(SonicAudioProcessor.SAMPLE_RATE_NO_CHANGE)
            }
        }

        return if (args == null) {
            method.invoke(audioSink)
        } else {
            method.invoke(audioSink, *args)
        }
    }
}


/**
 * 设置单个播放对象
 */
fun <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.playModel(
    model: Model,
    play: Boolean = true
) {
    models = mutableListOf(model)
    if (play) {
        prepare(0)
        play()
    }
}

/**
 * 判断播放器释放还在播放中
 */
val <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.isPlaying
    get() = when (currentModelState) {
        //播放中
        IPlayer.ModelState.Prepare,
        IPlayer.ModelState.Buffer,
        IPlayer.ModelState.Play -> true
        //暂停中
        IPlayer.ModelState.End,
        IPlayer.ModelState.Error,
        IPlayer.ModelState.Pause,
        IPlayer.ModelState.Stop -> false
    }

/**
 * 释放播放最后一个
 */
val <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.isLastIndex: Boolean
    get() {
        if (!hasModel) {
            return false
        }
        return currentIndex == models.lastIndex
    }

/**
 * 释放播放最后一个
 */
val <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.isFirstIndex: Boolean
    get() {
        if (!hasModel) {
            return false
        }
        return currentIndex == 0
    }

/**
 * 当前播放位置
 */
val <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.currentIndex: Int
    get() {
        if (!hasModel) {
            return -1
        }
        return models.indexOf(currentModel)
    }

/**
 * 是否正在缓存中
 */
val <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.isBuffering
    get() = when (currentModelState) {
        //播放中
        IPlayer.ModelState.Prepare,
        IPlayer.ModelState.Buffer -> true
        //暂停中
        IPlayer.ModelState.End,
        IPlayer.ModelState.Error,
        IPlayer.ModelState.Pause,
        IPlayer.ModelState.Play,
        IPlayer.ModelState.Stop -> false
    }

fun <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.seekPercentage(percentage: Float) {
    if (duration > 0L && percentage in 0f..1f) {
        currentPosition = ((duration * percentage).toLong())
    }
}

/**
 * 重试
 */
fun <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.retry(position: Long = currentPosition) {
    val index = models.indexOf(currentModel)
    prepare(index)
    play()
    currentPosition = position
}


/**
 * 绑定生命周期
 */
fun <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.bindLifecycleHelper(
    lifecycle: Lifecycle,
    impl: PlayerBindLifecycleHelper<Media, Model> = PlayerBindLifecycleHelper(this)
): PlayerBindLifecycleHelper<Media, Model> {
    lifecycle.addObserver(impl)
    return impl
}

/**
 * 绑定下载管理器
 */
fun <Media, Model : IPlayer.IModel<Media>> IPlayer<Media, Model>.bindDownloadHelper(impl: IPlayerDownload<Media>): IPlayerDownload<Media> {
    this.downloader = impl
    return impl
}

/**
 * raw转路径
 */
fun rawResToPath(@RawRes rawRes: Int) = "rawresource:///${rawRes}"

/**
 * assets转路径
 */
fun assetResToPath(assetsPath: String) = "asset:///${assetsPath.trimStart('/')}"

/**
 * 获取当前设备支持的最大音频采样率
 */
fun Context.getSupportMaxOutputSampleRate(): Int {
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val sampleRate = audioManager.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE).toInt()
    return try {
        val hiddenClass = AudioFormat::class.java
        val metaGetDeclaredMethod = Class::class.java.getDeclaredMethod(
            "getDeclaredField",
            String::class.java
        )
        val field = metaGetDeclaredMethod.invoke(hiddenClass, "SAMPLE_RATE_HZ_MAX") as Field
        field.get(null)?.toString()?.toInt() ?: sampleRate
    } catch (e: Throwable) {
        e.printStackTrace()
        sampleRate
    }
}

